/*
 * DataLinkLaye.cpp
 *
 *  Created on: Mar 14, 2013
 *      Author: Xi
 */

#include "DataLinkLayer.h"
#include "Utilities.h"
#include "PhysicalLayer.h"
#include "ApplicationLayer.h"
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include"Client.h"
#include<sstream>
#include<math.h>
#include <fstream>

Client c;
int expected_ACK = 1;
pthread_t recvThread; /* recv thread */
pthread_t sendThread; /* send thread */
pthread_t timerThread[8];

int ACKIndex = 1; /*index for next frame's header*/
vector<string> queue;
vector<string> window; /*if the window is full, put frame in queue*/
vector<string> frameChunk; /*store frame chunks and pack up to packet then pass to application layer*/
vector<string> sendQueue;
bool recvFlag = true; /*recv only called once, then runs recursively*/
int recvfd; /*fd for socket*/
int fdptr; /*pointer to fd*/
int errorRate = 0; /*error rate*/

/**
 * Default constructor, just instantiate object
 */
DataLinkLayer::DataLinkLayer() {

}

/**
 * Default destructor
 */
DataLinkLayer::~DataLinkLayer() {

}

/**
 * Return payloads from a set of frames
 * @param frames
 * @return 
 */
string getPayload(vector<string> frames) {
    string packet;
    int i = 0;
    for (i = 0; i < frames.size(); i++) {
        string payload;
        payload = frames.at(i).substr(7);
        payload = payload.substr(0, payload.size() - 3);
        packet += payload;
    }
    return packet;
}

/**
 * Return payload from a frame
 * @param frame
 * @return 
 */
string getPayload(string frame) {

    string packet = "";
    string payload = "";
    payload = frame.substr(7);
    payload = payload.substr(0, payload.size() - 3);
    packet += payload;

    return packet;
}

/**
 * Return ACK from a frame
 * @param frame
 * @return 
 */
string getACK(string frame) {

    string ack = frame.substr(5, 2);
    return ack;
}

/**
 * Return length in header from a frame
 * @param frame
 * @return 
 */
string getLength(string frame) {
    string length = frame.substr(1, 2);
    return length;
}

/**
 * Return sequence from frame
 * @param frame
 * @return 
 */
string getSeq(string frame) {
    string seq = frame.substr(3, 2);
    return seq;
}

void* send(void *strPtr) {
    string packet = sendQueue.at(0);
    sendQueue.erase(sendQueue.begin());
    ApplicationLayer app;
    app.toCmd(packet, " ", recvfd);
}

/**
 * Timer for frame resend event, if received expecting ACK, cancel the thread
 * @param ackPtr
 * @return 
 */
void* startTimer(void* ackPtr) {

    int ack = *((int*) ackPtr);
    
    usleep(300000); //300ms

    /*re-send frame*/
    PhysicalLayer phys;
    int i = ack - expected_ACK;
    for(int j=i; j<window.size(); j++)
	phys.fromDLLayer(window.at(j), fdptr);
}

/**
 * Key function for data link layer- it runs recursively to receiving frame from physical layer, then judge whether it's ACKs or frames;
 * For ACKs- if arriving ACK equals to expected ACK, expand expected ACK, else goto next loop wait for timer event resend the frame;
 * For frames- first do checksum, if the frame is correct return the ACK back, then pass payload to application layer, else simply end the code.
 * @param fdptr
 * @return 
 */
void* recvACK(void *fdptr) {

    Utilities u;
    u.echoDebugInfo("Inside DataLinkLayer recvACK(), runs recursively");
    recvfd = *((int*) fdptr);
    int fd = recvfd;

    PhysicalLayer phys;
    bool run = true;

    while (run) {
        string frame = phys.toDLLayer(fd);
        if (frame.length() <= 2 && frame.length() > 0) {

            /*arrive frame is ACK*/
            int ACK = atoi(frame.c_str());
            cout << "ACK  " << ACK << endl;
            if (ACK == expected_ACK) {
                /*stop timer*/
                pthread_cancel(timerThread[ACK - 1]);

                /*slide window*/
                cout << "current window size : " << window.size() << endl;
                window.erase(window.begin());

                stringstream sss;
                sss << ACK;
                string temp = sss.str();
                string deInfo = "Inside DataLinkLayer: received an ACK : " + temp;
                u.echoDebugInfo(deInfo);
                //cout << deInfo << endl;

                /*check queue, if not empty, push element in*/
                if (queue.size() > 0) {
                                        cout << queue.size() << endl;
                                        int size = 8 - window.size();
                                        int dif = size - queue.size();
                                        if (size > 0 && dif >= 0){
                                            for (int i = 0; i < queue.size(); i++) {
                                                window.push_back(queue.at(0));
       				                /*start timer*/
             				       string ackStr = getACK(queue.at(0));
               				       int currentACK = atoi(ackStr.c_str());
                  		  pthread_create(&timerThread[currentACK - 1], 0, &startTimer, (void*) &currentACK);
 		                   pthread_detach(timerThread[currentACK - 1]);

                                         PhysicalLayer phy;
                                                phy.fromDLLayer(queue.at(0), recvfd);
                                                queue.erase(queue.begin());
                                            }
					    } else if (size > 0 && dif < 0) {
                                            for (int i = 0; i < size; i++) {
                                                window.push_back(queue.at(0));
                                              /*start timer*/
               				     string ackStr = getACK(queue.at(0));
                  				  int currentACK = atoi(ackStr.c_str());
                  			  pthread_create(&timerThread[currentACK - 1], 0, &startTimer, (void*) &currentACK);
                   			 pthread_detach(timerThread[currentACK - 1]);

   			             PhysicalLayer phy;
                                                phy.fromDLLayer(queue.at(0), recvfd);
                                                queue.erase(queue.begin());
                                            }
                                           } 
               //     window.push_back(queue.at(0));
                //    PhysicalLayer phy;

                    /*start timer*/
                   /* string ackStr = getACK(queue.at(0));
                    int currentACK = atoi(ackStr.c_str());
                    pthread_create(&timerThread[currentACK - 1], 0, &startTimer, (void*) &currentACK);
                    pthread_detach(timerThread[currentACK - 1]);

                    phy.fromDLLayer(queue.at(0), recvfd);
                    queue.erase(queue.begin());
                    cout << "queue size :" << queue.size() << endl;
*/
                }
                /*update expected ACK*/
                if (expected_ACK == 8) {
                    expected_ACK = 1;
                } else
                    expected_ACK++;
                continue;
            } else {
                cout << "received an unexpected ACK!!" << endl;
                continue;
            }
        } else if (frame.length() >= 6) {

            /*received a frame*/
            //checksum
            int l = frame.length();
            int i, j;
            unsigned CRC, gx;
            CRC = 0;
            char *crcCheck; //checksum in hex
            char *crcRecv; //checksum in frame 
            crcCheck = (char*) malloc(2);
            crcRecv = (char*) malloc(2);
            gx = 0x107;

            crcRecv[0] = frame[l - 3];
            crcRecv[1] = frame[l - 2];
            crcRecv[2] = '\0';
            for (i = 0; i < l - 3; i++) //frame, from the first byte to last before checksum byte 	
            {
                CRC ^= frame[i];
                for (j = 0; j < 8; j++) {
                    if (CRC & 0x80)
                        CRC = (CRC << 1) ^ gx;
                    else
                        CRC = (CRC << 1);
                }
                CRC &= 0xff;
            }
            sprintf(crcCheck, "%02x", CRC);

            //cout << "CHECK: " << crcCheck << endl;
            //cout << "RECV: " << crcRecv << endl;
            if (strcmp(crcCheck, crcRecv) != 0) {
                //        u.echoDebugInfo("Inside DataLinkLayer: receive a damaged frame");
                cout << "Receive a damaged frame!!!!!!!!!!!!!!!!!!!!!" << endl;
                free(crcCheck);
                free(crcRecv);
                continue;
            }

            free(crcCheck);
            free(crcRecv);
            /*return ACK*/
            string ack = getACK(frame);

            /*check ACK correctness*/
            int ACKVal = atoi(ack.c_str());
            if (ACKVal > 8) {
                //                u.echoDebugInfo("Inside DataLinkLayer: receive a damaged frame");
                continue;
            } else {
                /*return ACK*/
                phys.fromDLLayer(ack, fd);
                /*get frame length*/
                string lengthStr = getLength(frame);
                int length = atoi(lengthStr.c_str());
                u.echoDebugInfo("Inside DataLinkLayer: received a frame");
                if (length == 3) {
                    /*push frame in vector, each 3 frames could(if no errors) forms a packet*/
                    //judge whether it's resend due to frame error
                    if (frameChunk.size() != 0 && frameChunk.size() < 3) {

                        frameChunk.push_back(frame);
                        /*received all frames*/

                        /* frame should arrive in order, such that:
                         * 
                         * ACK[n+1] - ACK[n] =  1  | 1  ||
                         * ACK[n+1] - ACK[n] = -7  | 1  ||
                         * ACK[n+1] - ACK[n] =  1  | -7 
                         * &&
                         * SEQ[n+1] - SEQ[n] = 1
                         *
                         */
                        if (frameChunk.size() == 3) {
                            string ACK1Str = getACK(frameChunk.at(0));
                            int ACK1 = atoi(ACK1Str.c_str());

                            string ACK2Str = getACK(frameChunk.at(1));
                            int ACK2 = atoi(ACK2Str.c_str());

                            string ACK3Str = getACK(frameChunk.at(2));
                            int ACK3 = atoi(ACK3Str.c_str());

                            string Seq1Str = getSeq(frameChunk.at(0));
                            int Seq1 = atoi(Seq1Str.c_str());

                            string Seq2Str = getSeq(frameChunk.at(1));
                            int Seq2 = atoi(Seq2Str.c_str());

                            string Seq3Str = getSeq(frameChunk.at(2));
                            int Seq3 = atoi(Seq3Str.c_str());

                            /*calculate value*/
                            int a1 = ACK2 - ACK1;
                            int a2 = ACK3 - ACK2;

                            int b1 = Seq2 - Seq1;
                            int b2 = Seq3 - Seq2;

                            if (((a1 == 1 && a2 == 1) || (a1 == -7 && a2 == 1) || (a1 == 1 && a2 == -7)) && (b1 == 1 && b2 == 1)) {
                                string packet = getPayload(frameChunk);
                                ApplicationLayer appl;
                                frameChunk.clear();
                                sendQueue.push_back(packet);
                                pthread_create(&sendThread, NULL, &send, (void*) &packet);
                                pthread_detach(sendThread);
                                void* retval;
                                pthread_join(sendThread, &retval);
                                continue;
                            } else {
                                cout << "$$$$$$$$$$$$$$$$$$$$$$$$$$$$" << endl; //errors in some frames, clear all current frames
                                frameChunk.clear();
                            }
                        }
                    } else if (frameChunk.size() == 0) {
                        /*first frame*/
                        frameChunk.push_back(frame);
                        continue;
                    }
                } else if (length == 2) {

                    /*push frame in vector, each 3 frames could(if no errors) forms a packet*/
                    //judge whether it's resend due to frame error
                    if (frameChunk.size() != 0 && frameChunk.size() < 2) {

                        frameChunk.push_back(frame);

                        /*received all frames*/

                        /* frame should arrive in order, such that:
                         * 
                         * ACK[n+1] - ACK[n] =  1  | -7 
                         * &&
                         * SEQ[n+1] - SEQ[n] = 1
                         *
                         */
                        if (frameChunk.size() == 2) {

                            string ACK1Str = getACK(frameChunk.at(0));
                            int ACK1 = atoi(ACK1Str.c_str());

                            string ACK2Str = getACK(frameChunk.at(1));
                            int ACK2 = atoi(ACK2Str.c_str());

                            string Seq1Str = getSeq(frameChunk.at(0));
                            int Seq1 = atoi(Seq1Str.c_str());

                            string Seq2Str = getSeq(frameChunk.at(1));
                            int Seq2 = atoi(Seq2Str.c_str());

                            /*calculate value*/
                            int a1 = ACK2 - ACK1;

                            int b1 = Seq2 - Seq1;

                            if ((a1 == 1 || a1 == -7) && (b1 == 1)) {
                                string packet = getPayload(frameChunk);
                                ApplicationLayer appl;
                                frameChunk.clear();
                                sendQueue.push_back(packet);
                                pthread_create(&sendThread, NULL, &send, (void*) &packet);
                                pthread_detach(sendThread);
                                void* retval;
                                pthread_join(sendThread, &retval);
                                continue;
                            } else {
                                //errors in some frames, clear all current frames
                                frameChunk.clear();
                            }
                        }
                    } else if (frameChunk.size() == 0) {
                        /*first frame*/
                        frameChunk.push_back(frame);
                        continue;
                    }
                } else if (length == 1) {
                    string packet = getPayload(frame);
                    ApplicationLayer appl;
                    sendQueue.push_back(packet);
                    pthread_create(&sendThread, NULL, &send, (void*) &packet);
                    pthread_detach(sendThread);
                    void* retval;
                    pthread_join(sendThread, &retval);
                    continue;
                }
            }
            continue;
        }
        continue;
    }
    pthread_exit(NULL);

   // cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl;
}

/**
 * return random number in range min - max
 * @param min
 * @param max
 * @return 
 */
int randomGenerator(int min, int max) {
    int random, max_value = 100, min_value = 0;
    random = rand() % ((max_value + min_value) + min_value);

    return random;
}

/**
 * Damage ACKs, replace ACK to 99
 * @param s
 * @return 
 */
string damageACK(string s) {
    Utilities u;
    u.echoDebugInfo("Will damage the ACK");
    /*replace ACK to 99*/
    string f = s.replace(5, 2, "99");

    return f;
}

/**
 * Damage ACKs, replace ACK to 99
 * @param s
 * @return 
 */
string damageFrame(string s) {
    Utilities u;
    u.echoDebugInfo("Will damage the frame");
    /*replace checksum to 99*/
    string f = s.replace(s.length() - 4, 2, "99");

    return f;
}

/**
 * Set error rate, damage either ACK or checksum if random number less than error rate
 * @param v
 */
void DataLinkLayer::setErrorRate(int v) {
    errorRate = v;
}

/**
 * Receive packet from Application layer, first break into several frames, then add header for each frame, finally do stuffing and pass them to physical layer.
 * Here we use go-back-8.
 * @param packet
 * @param fd
 * @return 
 */
string DataLinkLayer::fromAppLayer(string packet, int fd) {

    /*debug info*/
    utl.echoDebugInfo("Inside DataLinkLayer::fromAppLayer()");
    // header
    vector<string> payloads, frames;
    frames = utl.splitString(packet, ' ', 90);
    if (frames.empty())
        return "a";
    else {
        int i = 0;
        for (i = 0; i < frames.size(); i++) {
            string header;
            stringstream s;
            int size = frames.size();
            s << size;
            string len = s.str();

            header = "~0" + len + "0";
            stringstream ss;
            int v = i + 1;
            ss << v;
            string seq = ss.str();
            header += seq; //5 bytes for, stuffing byte, length and sequence

            // append ACK
            header += "0";
            stringstream ss1;
            ss1 << ACKIndex;
            string ACK = ss1.str();
            header += ACK; //ACK

            /*expand index*/
            if (ACKIndex == 8)
                ACKIndex = 1;
            else
                ACKIndex++;

            string temp = frames.at(i);
            string frame = header;
            frame += temp; // head and payload

            /*footer*/

            int l;
            unsigned int CRC, gx;
            CRC = 0;
            char *crc; //hex crc
            crc = (char *) malloc(2);
            gx = 0x107; //polynomial G(X)
            l = frame.length();
            //	cout << "frame: " << frame << "length: " << frame.length() << endl;
            int i, j;
            for (i = 0; i < l; i++) {
                CRC ^= frame[i];
                for (j = 0; j < 8; j++) {
                    if (CRC & 0x80) {
                        CRC = (CRC << 1) ^ gx;
                    } else {
                        CRC <<= 1;
                    }
                }
            }
/*            if (randomGenerator(0, 100) < errorRate)  //simulate noise error
            {
                CRC ^= 1;
            }
*/
            sprintf(crc, "%02x", CRC);
            string crcStr(crc, 2);
            frame += crcStr;
            frame += "~"; // stuffing
            /*update vector*/
            payloads.push_back(frame);
        }


        /*send out frames*/
        for (int i = 0; i < payloads.size(); i++) {

            string frame;
            frame = payloads.at(i);

            if (window.size() < 8 && queue.size() == 0) {

                /*store frame in window*/
                window.push_back(frame);
                /*damage frame*/
                string dmFrame;
                int random = randomGenerator(0, 100);
                if (random < errorRate) {
                    if (random < (int) (errorRate / 2))
                        dmFrame = damageACK(frame);
                    else
                        dmFrame = damageFrame(frame);
                    /*start timer*/
                    string ackStr = getACK(frame);
                    int currentACK = atoi(ackStr.c_str());
		    //currentACK++;
                    pthread_create(&timerThread[currentACK - 1], 0, &startTimer, (void*) &currentACK);
                    pthread_detach(timerThread[currentACK - 1]);
			phy.fromDLLayer(dmFrame, fd); //send  out  frame
                } else {
                    /*start timer*/
                    string ackStr = getACK(frame);
                    int currentACK = atoi(ackStr.c_str());
                    pthread_create(&timerThread[currentACK - 1], 0, &startTimer, (void*) &currentACK);
                    pthread_detach(timerThread[currentACK - 1]);

                    /*send out frame*/
                    phy.fromDLLayer(frame, fd);
                }


                /*receive in parallel*/
                fdptr = fd;
                int err;
                if (recvFlag == true) {
                    err = pthread_create(&recvThread, NULL, &recvACK, (void*) &fdptr);
                    pthread_detach(recvThread);
                    recvFlag = false;
                }

            } else {
                cout << "Window is full!!!" << endl;
                queue.push_back(frame);
            }
        }
        return "a";
    }
}

/**
 * Receive function in data link layer, since we want run recv() recursively and separately from main thread, this function only called once by server when new client connected
 * @param fd
 * @return 
 */
string DataLinkLayer::toAppLayer(int fd) {
    utl.echoDebugInfo("Inside DataLinkLayer::toAppLayer()");
    fdptr = fd;
    int err;
    err = pthread_create(&recvThread, NULL, &recvACK, (void*) &fdptr);
    pthread_detach(recvThread);
    recvFlag = false;

    return "b";
}

void DataLinkLayer::sendPic(string param) {
	param+=".txt";
    ifstream infile(param.c_str());
    // get size of file
    infile.seekg(0, infile.end);
    int size = infile.tellg();
    infile.seekg(0);


    char* buffer;
    buffer = (char*) malloc(size);
    infile.read(buffer, size);
    infile.close();

    ApplicationLayer app;
    app.fromPic(buffer, recvfd, size);
    free(buffer);
}
